package com.opennews.openplatform.familyexpenseapi.service

import com.opennews.openplatform.familyexpenseapi.config.AppConfigProperties
import com.opennews.openplatform.familyexpenseapi.entity.Tables.USER
import com.opennews.openplatform.myspringbootcore.util.DateHelper
import com.opennews.openplatform.myspringbootcore.util.nextRandomIntInRange
import org.jooq.DSLContext
import org.springframework.core.env.Environment
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.io.FileOutputStream
import java.util.*

@Service
class FileService(
    private val appConfigProperties: AppConfigProperties,
    private val dslContext: DSLContext,
    private val env: Environment,
    private val userService: UserService,
) {
    /**
     * Uploads the avatar by using the file content in base64 format.
     *
     * @param userId: The string of the user'id.
     * @param originalFilename: The string of original filename of avatar.
     * @param base64Content: The file content in base64 format.
     * @return The string of new avatar full url.
     */
    fun uploadAvatarByBase64(userId: String, originalFilename: String, base64Content: String): String {
        // Generates the new filename with timestamp.
        val filename = generateFilename(originalFilename)

        // Decodes the input base64 content.
        val bytes = Base64.getDecoder().decode(base64Content)

        // Gets the avatar file saved path.
        val uploadAvatarPath = getUploadedAvatarPath()

        // The file instance of new avatar.
        val uploadAvatarFile = File(uploadAvatarPath, filename)

        // Writes the bytes to new avatar file.
        try {
            FileOutputStream(uploadAvatarFile).use { stream ->
                stream.write(bytes)
                stream.flush()
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
        }

        // Updates user avatar in the data source and removes old avatar file.
        updateUserAvatar(userId, uploadAvatarPath, filename)

        // Returns the filename as output.
        // The filename type is GString. That's why toString used here.
        return userService.getAvatarUrl(filename)
    }

    /**
     * Uploads the avatar by using the uploaded file.
     *
     * @param userId: The string of the user'id.
     * @param file: The uploaded file of avatar.
     * @return The string of new avatar full url.
     */
    fun uploadAvatar(userId: String, file: MultipartFile): String {
        // https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html
        // Gets the file original file name.
        val originalFilename = file.originalFilename ?: ""

        // Generates the new filename with timestamp.
        val filename = generateFilename(originalFilename)

        // Checks if the file is empty.
        if (file.isEmpty) {
            return ""
        }

        // Gets the avatar file saved path.
        val uploadAvatarPath = getUploadedAvatarPath()

        // The file instance of new avatar.
        val uploadAvatarFile = File(uploadAvatarPath, filename)

        // Transfers the file to the path.
        file.transferTo(uploadAvatarFile)

        // Updates user avatar in the data source and removes old avatar file.
        updateUserAvatar(userId, uploadAvatarPath, filename)

        // Returns the filename as output.
        return userService.getAvatarUrl(filename)
    }

    /**
     * Uploads file with scaling width.
     *
     * @param file: The uploaded file.
     * @param directoryName: The name of the directory where the file is uploaded to.
     * @param targetWidth: The target image width. If scaling is needed, please specify this value.
     * @return The string of new upload filename.
     */
    fun uploadFile(file: MultipartFile, directoryName: String, targetWidth: Int): String {
        // https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html
        // Gets the file original file name.
        val originalFilename = file.originalFilename ?: ""

        // Generates the new filename with timestamp.
        val filename = generateFilename(originalFilename)

        // Checks if the file is empty.
        if (file.isEmpty) {
            return ""
        }

        // Gets the upload file saved path.
        val uploadPath = getUploadedDirectoryPath(directoryName)

        // The file instance of new upload.
        val uploadFile = File(uploadPath, filename)

        // Transfers the file to the path.
        file.transferTo(uploadFile)

        // Returns the filename as output.
        return filename
    }

    /**
     * Uploads file without scaling.
     *
     * @param file: The uploaded file.
     * @param directoryName: The name of the directory where the file is uploaded to.
     * @return The string of new upload filename.
     */
    fun uploadFile(file: MultipartFile, directoryName: String): String {
        return uploadFile(file, directoryName, -1)
    }

    /**
     * For demo purpose only at this moment.
     *
     * @param files List of MultipartFile.
     * @return List of new filenames.
     */
    fun uploadFiles(files: List<MultipartFile>): List<String> {
        val newFilenames = mutableListOf<String>()

        // Iterate through each file in the list
        // https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html
        for (file in files) {
            // Gets the file original file name.
            var filename = file.originalFilename ?: ""

            // Gets the last position of '.'. This is for getting the file extension.
            val lastDotIndex = filename.lastIndexOf(".")

            // If the filename contains extension then we need to keep it.
            filename = if (lastDotIndex > -1) {
                "${
                    DateHelper.format(
                        DateHelper.getCurrentLocalDateTime(),
                        DateHelper.LONG_TIMESTAMP
                    )
                }-${filename.substring(lastDotIndex + 1)}"
            } else {
                "${
                    DateHelper.format(
                        DateHelper.getCurrentLocalDateTime(),
                        DateHelper.LONG_TIMESTAMP
                    )
                }-${nextRandomIntInRange(100000, 999)}"
            }

            // Checks if the file is empty.
            if (!file.isEmpty) {
                val tempFile = File("D:/$filename")
                file.transferTo(tempFile)

                // Saves the temp file path.
                newFilenames.add(tempFile.path)
            }
        }

        return newFilenames
    }

    /**
     * Deletes the file with the given filename.
     *
     * @param filename: The name of the file to delete.
     */
    fun delete(filename: String) {
        val file = File(getUploadedAvatarPath(), filename)

        if (file.exists()) {
            file.delete()
        }
    }

    /**
     * Generates the new avatar filename based on original file extension name.
     *
     * @param originalFilename: The string of uploaded file original filename.
     * @return The string of new generated filename with original file extension name.
     */
    private fun generateFilename(originalFilename: String): String {
        // Generates the new filename with timestamp.
        val filename = "${DateHelper.format(DateHelper.getCurrentLocalDateTime(), DateHelper.LONG_TIMESTAMP)}-" +
                nextRandomIntInRange(100000, 999999)

        // Gets file extension.
        val extension = getFileExtension(originalFilename)

        // If the filename contains extension, then we need to keep it.
        return if (extension.isNotEmpty()) "$filename.$extension" else filename
    }

    /**
     * Gets extension from filename.
     *
     * @param filename: The string of input filename.
     * @return The string of file extension. Example: "jpg". Please notice the leading dot is NOT included.
     */
    private fun getFileExtension(filename: String): String {
        // Gets the last position of '.'. This is for getting the file extension.
        val lastDotIndex = filename.lastIndexOf(".")

        // If the filename contains extension.
        return if (lastDotIndex > -1) filename.substring(lastDotIndex + 1) else ""
    }

    /**
     * Gets file instance of uploaded avatar path.
     *
     * @return The file instance of the new avatar path which avatar files are saved to.
     */
    private fun getUploadedAvatarPath(): File {
        return getUploadedDirectoryPath(appConfigProperties.resource.avatarDirectoryName ?: "")
    }

    /**
     * Gets file instance of uploaded directory path.
     *
     * @return The file instance of the new directory path where uploaded files are saved to.
     */
    private fun getUploadedDirectoryPath(directoryName: String): File {
        // Gets the upload avatar file path.
        val uploadPath = File(appConfigProperties.resource.upload.rootFilePath, directoryName)

        // If the path does not exist, then creates it.
        if (!uploadPath.exists()) {
            uploadPath.mkdirs()
        }

        return uploadPath
    }

    /**
     * Updates user's avatar filename and deletes old avatar file.
     *
     * @param userId: The string of the user id.
     * @param uploadAvatarPath: The file instance of the path where avatar files are saved.
     * @param filename: The new avatar filename.
     */
    private fun updateUserAvatar(userId: String, uploadAvatarPath: File, filename: String) {
        // Gets the user for deleting old avatar file and updating new one.
        val currentAvatarFilename = dslContext.selectFrom(USER)
            .where(USER.ID.eq(userId))
            .fetchOne(USER.AVATAR_FILENAME)

        // Deletes the old avatar file if needed.
        if (!currentAvatarFilename.isNullOrBlank()) {
            val currentAvatarFile = File(uploadAvatarPath, currentAvatarFilename)

            if (currentAvatarFile.exists()) {
                currentAvatarFile.delete()
            }
        }

        // Saves the new avatar filename.
        dslContext.update(USER)
            .set(USER.AVATAR_FILENAME, filename)
            .set(USER.LAST_UPDATED, DateHelper.getCurrentLocalDateTime())
            .where(USER.ID.eq(userId))
            .execute()
    }
}